﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using Amazon.S3;
using System.IO;

namespace NAntExt.Aws.Tasks
{
    /// <summary>
    /// Invalidates keys on Amazon CloudFront.
    /// </summary>
    [TaskName(CloudFrontInvalidateTask.TaskName)]
    public class CloudFrontInvalidateTask : AwsTaskBase
    {
        /// <summary>
        /// The name of this task.
        /// </summary>
        public const string TaskName = "cloudfront-invalidate";
        
        /// <summary>
        /// The unique Id associated with the CloudFront distribution to send an invalidation
        /// request.
        /// </summary>
        [TaskAttribute("distributionId", Required = true)]
        [StringValidator(AllowEmpty = false)]
        public string DistributionId { get; set; }

        /// <summary>
        /// The file to load the keys to invalidate in CloudFront.
        /// </summary>
        [TaskAttribute("in", Required = true)]
        [StringValidator(AllowEmpty = true)]
        public string In { get; set; }

        /// <summary>
        /// Primary entry point for this task.
        /// </summary>
        protected override void ExecuteTask()
        {
            try
            {
                if (!File.Exists(this.In))
                {
                    throw new FileNotFoundException("In attribute points to a file that does not exist. It must point to a file that exists.", this.In);
                }

                var invalidateKeys = new List<string>(File.ReadAllLines(this.In));
                InvalidatePaths(invalidateKeys);
            }
            catch (Exception ex)
            {
#if DEBUG
                System.Diagnostics.Debug.WriteLine(ex, "Error");
#endif

                if (this.FailOnError)
                    throw new BuildException(string.Format("{0} task failed during execution due to an unhandled exception.", TaskName), this.Location, ex);
                else
                    Log(Level.Error, "{0} task failed during execution due to an unhanled exception. ex = {0}", TaskName, ex);
            }
        }

        private void InvalidatePaths(List<string> invalidateKeys)
        {
            int invalidationFileCount = 0;
            if (invalidateKeys.Count > 0)
            {
                using (var client = new Amazon.CloudFront.AmazonCloudFrontClient(this.AccessKeyId, this.SecretAccessKey))
                {
                    const int maximuminvalidationKeysPerBatch = 1000;
                    for (int indexStart = 0; indexStart < invalidateKeys.Count; indexStart += maximuminvalidationKeysPerBatch)
                    {
                        // let's ensure there is a free invalidation request before we continue else everything will blow up since
                        // amazon only allows 3x invalidation requests running at the same time on only 1000 per request
                        PollForFreeInvalidationRequest();

                        var invalidationKeysBatch = invalidateKeys.GetRange(indexStart, Math.Min(maximuminvalidationKeysPerBatch, invalidateKeys.Count - indexStart));
                        var batchReference = Guid.NewGuid().ToString();

                        // send an invalidation request to Amazon CloudFront
                        var invalidationResponse = client.PostInvalidation(new Amazon.CloudFront.Model.PostInvalidationRequest()
                        {
                            DistributionId = this.DistributionId,
                            InvalidationBatch = new Amazon.CloudFront.Model.InvalidationBatch()
                            {
                                CallerReference = batchReference,
                                Paths = invalidationKeysBatch
                            }
                        });

                        Log(Level.Info, "Invalidation request submitted to CloudFront.   DistributionId={0}, BatchReference={1}", this.DistributionId, batchReference);

                        foreach (var s3Key in invalidationKeysBatch)
                        {
                            Log(Level.Info, "Key invalidated on CloudFront. Key = {0}", s3Key);
                            invalidationFileCount++;
                        }
                    }
                }
            }

            Log(Level.Info, "Number of items invalidated: {0}", invalidationFileCount);
        }

        private void PollForFreeInvalidationRequest()
        {
            using (var client = new Amazon.CloudFront.AmazonCloudFrontClient(this.AccessKeyId, this.SecretAccessKey))
            {
                while (true)
                {
                    var invalidationListResponse = client.GetInvalidationList(new Amazon.CloudFront.Model.GetInvalidationListRequest()
                    {
                        DistributionId = this.DistributionId
                    });

                    if (invalidationListResponse.Summaries.FindAll(summary => summary.Status == "InProgress").Count < 3)
                    {
                        // there is a free invalidation request
                        break;
                    }

                    // write something useful
                    Log(Level.Info, "Waiting: No available CloudFront invalidation requests available. Waiting for invalidation request to become available. Will recheck in 15 seconds.");

                    // sleep and wait for a free invalidation request
                    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(15));
                }
            }
        }
                
    }
}
